package za.org.jcicada.watchdog;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import za.org.jcicada.cfgmngr.IConfigurationClient;
import za.org.jcicada.cfgmngr.IConfigurationServer;
import za.org.jcicada.cfgmngr.impl.DefaultConfigurationClient;
import za.org.jcicada.genss.ServiceURN;
import za.org.jcicada.genss.SystemURN;
import za.org.jcicada.genss.lifecycle.ILifeCycleClient;
import za.org.jcicada.genss.lifecycle.ILifeCycleServer;
import za.org.jcicada.genss.lifecycle.impl.DefaultLifeCycleClient;
import za.org.jcicada.genss.lifecycle.impl.LifeCycleServerProxy;
import za.org.jcicada.genss.messages.Result;
import za.org.jcicada.genss.messages.SuccessCode;
import za.org.jcicada.genss.sensor.IStatusStrategy;
import za.org.jcicada.genss.sensor.impl.InvalidSensorValue;
import za.org.jcicada.genss.sensor.impl.Sensor;
import za.org.jcicada.genss.sensor.impl.StatusStrategyFactory;
import za.org.jcicada.genss.sensor.impl.TypeDiscrete;

public class SystemWatchdog {

	/** Logger object used for logging messages. */
	private static final Log logger = LogFactory.getLog(SystemWatchdog.class);

	private SystemURN systemURN;
	
	private ServiceURN serviceURN;
	
	private Sensor sensor;
	
	private Timer timer;
	
	private TimerTask pingTimerTask;
	
	private IConfigurationServer configServer;
	
	private ILifeCycleServer lifeCycleServer = null;
	
	private ILifeCycleClient lifeCycleClient = new LifeCycleClient();
	
	private IConfigurationClient configurationClient = new ConfigurationClient();
	
	enum PingState { PINGED, RESPONDED };
	
	private PingState pingState;
	
	private long pingPeriod_ms;
	
	private long pingTimeout_ms;
	
	public SystemWatchdog(IConfigurationServer configServer, SystemURN systemURN, Timer timer, long pingPeriod_ms, long pingTimeout_ms, String statusStrategyString) throws URISyntaxException {
		this.timer = timer;
		this.pingPeriod_ms = pingPeriod_ms;
		this.pingTimeout_ms = pingTimeout_ms;
		this.configServer = configServer;
		this.systemURN = systemURN;
		this.serviceURN = new ServiceURN(systemURN, "lifecycle");
		this.sensor = new Sensor(TypeDiscrete.getInstance(StateEnum.class), "watchdog", String.format("%s watchdog sensor", systemURN.getSystemName()), "");
		IStatusStrategy statusStrategy = StatusStrategyFactory.createStatusStrategy(this.sensor, statusStrategyString);
		this.sensor.setStatusStrategy(statusStrategy);
		setState(StateEnum.UNKNOWN);
	}
	
	public void shutdown() {
		if (configServer != null) {
			configServer.informDisconnect(configurationClient);
			configServer = null;
		}
		disconnectLifeCycleServer();
		setState(StateEnum.UNKNOWN);
	}
	
	public ILifeCycleClient getLifeCycleClient() {
		return lifeCycleClient;
	}
	
	public IConfigurationClient getConfigurationClient() {
		return configurationClient;
	}
	
	public Sensor getSensor() {
		return sensor;
	}
	
	public void systemRegistered() {
		setState(StateEnum.UNKNOWN);
		configServer.requestLocateService(configurationClient, serviceURN, "tcp");
	}
	
	public void systemDeregistered() {
		setState(StateEnum.OFF);
		lifeCycleServer = null;
	}
	
	private void setState(StateEnum state) {
		try {
			String stateString = state.toString();
			if (sensor.getValue() == null || sensor.getValue().getValue() == null || !sensor.getValue().getValue().equals(stateString)) {
				sensor.setValue(stateString);
			}
		} catch (InvalidSensorValue ex) {
			logger.error(ex);
		}
	}
	
	private synchronized void requestPing() {
		pingState = PingState.PINGED;
		lifeCycleServer.requestPing(lifeCycleClient, systemURN);
		timer.schedule(new TimerTask() {

			@Override
			public void run() {
				if (pingState != PingState.RESPONDED) {
					setState(StateEnum.UNRESPONSIVE);
					logger.warn(String.format("%s unresponsive so locating again", SystemWatchdog.this.serviceURN));
					disconnectLifeCycleServer();
					configServer.requestLocateService(configurationClient, SystemWatchdog.this.serviceURN, "tcp");
					cancel();
				}
			}
			
		}, pingTimeout_ms);
	}
	
	private synchronized void disconnectLifeCycleServer() {
		if (lifeCycleServer != null) {
			lifeCycleServer.informDisconnect(lifeCycleClient);
			lifeCycleServer = null;
		}
	}
	
	/*************************************************************************/
	
	private class LifeCycleClient extends DefaultLifeCycleClient {
		
		@Override
		public synchronized void replyPing(Result result, SystemURN systemURN) {
			pingState = PingState.RESPONDED;
			setState(StateEnum.RESPONSIVE);
		}
		
	}
	
	private class ConfigurationClient extends DefaultConfigurationClient {
		
		@Override
		public void replyLocateService(Result result, ServiceURN serviceURN, URI locationURI) {
			if (!result.getCode().equals(SuccessCode.COMPLETE_SUCCESS)) {
				logger.error(String.format("RequestLocateService code = %s: %s", result.getCode().toString(), result.getMessage()));
				timer.schedule(new TimerTask() {

					@Override
					public void run() {
						logger.info("Try again to get " + SystemWatchdog.this.serviceURN.toString());
						configServer.requestLocateService(ConfigurationClient.this, SystemWatchdog.this.serviceURN, "tcp");
					}
					
				}, pingPeriod_ms);
				return;
			}
			if (!serviceURN.equals(SystemWatchdog.this.serviceURN)) {
				logger.error(String.format("Unrecognized serviceURN: %s, expected %s", serviceURN, SystemWatchdog.this.serviceURN));
				return;
			}
			lifeCycleServer = new LifeCycleServerProxy(locationURI);
			if (pingTimerTask != null) {
				pingTimerTask.cancel();
			}
			timer.scheduleAtFixedRate(pingTimerTask = new TimerTask() {

				@Override
				public void run() {
					if (lifeCycleServer == null) {
						cancel();
					} else {
						requestPing();
					}
				}
				
			}, 0l, pingPeriod_ms);
		}

	}
	
}
